/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun * Microsystems, Inc. All Rights Reserved. */ package org.netbeans.modules.properties; import java.io.*; import java.util.*; import java.beans.PropertyChangeListener; import javax.swing.text.StyledDocument; import javax.swing.text.Position; import org.openide.TopManager; import org.openide.text.NbDocument; import org.openide.text.PositionRef; import org.openide.text.PositionBounds; import org.openide.filesystems.FileObject; import org.openide.loaders.DataObject; import org.openide.loaders.DataObjectNotFoundException; import org.openide.util.io.NullOutputStream; /** Parser of Java source code. It generates the hierarchy * of the implementations of elements. * * @author Petr Jiricka, Petr Hamernik */ class PropertiesParser { // ===================== Fields ============================== /** PropertiesFileEntry for which source is this parser created. */ PropertiesFileEntry pfe; /** Appropriate properties editor - used for creating the PositionRefs */ PropertiesEditorSupport editor; /** Properties element */ PropertiesStructure propStruct; /** Input stream */ PropertiesRead input; // ===================== Main methods and constructors ================== /** Private constructor. * @param pfe FileEntry where the properties file is stored. * @exception IOException if any i/o problem occured during reading */ public PropertiesParser(PropertiesFileEntry pfe) throws IOException { input = createReader(pfe); this.pfe = pfe; editor = pfe.getPropertiesEditor(); } /** Creates new input stream from the file object. * Finds the properties data object, checks if the document is loaded * and creates the stream either from the file object or from the document. * @param fo fileobject with the source * @param store if there is required the building and storing the elements * hierarchy * @exception IOException if any i/o problem occured during reading */ private static PropertiesRead createReader(PropertiesFileEntry pfe) throws IOException { PropertiesEditorSupport editor = pfe.getPropertiesEditor(); if (editor.isDocumentLoaded()) { // loading from the memory (Document) final javax.swing.text.Document doc = editor.getDocument(); final String[] str = new String[1]; // safely take the text from the document doc.render(new Runnable() { public void run() { try { str[0] = doc.getText(0, doc.getLength()); } catch (javax.swing.text.BadLocationException e) { // impossible } } }); return new PropertiesRead(str[0]); } else { // loading from the file InputStream is = new PropertiesEditorSupport.NewLineInputStream(pfe.getFile().getInputStream()); return new PropertiesRead(is); } } /** Parses the file - this method starts the parser. * After super.parseFile finish, the result is set to * the appropriate SourceElementImpl. */ public void parseFile() { try { PropertiesStructure ps = parseFileMain(input); input.close(); pfe.getHandler().setPropertiesStructure(ps); } catch (IOException e) { // parsing failed, the old copy remains valid // if there is no old copy, notify user // PENDING notify the user } } private PropertiesStructure parseFileMain(PropertiesRead in) throws IOException { ArrayMapList aml = new ArrayMapList(); while (true) { Element.ItemElem elem = readNextElem(in); if (elem == null) break; else { // add at the end of the list aml.add(elem.getKey(), elem); } } return new PropertiesStructure(createBiasBounds(0, in.position), aml); } /** Returns the next element or <code>null</code> if the end of the stream occurred */ private Element.ItemElem readNextElem(PropertiesRead in) throws IOException { Element.CommentElem commE; Element.KeyElem keyE; Element.ValueElem valueE; int charRead; int begPos = in.position; // read the comment int keyPos = begPos; FlaggedLine fl = in.readLineExpectComment(); StringBuffer comment = new StringBuffer(); boolean firstNull = true; while (fl != null) { firstNull = false; if (fl.flag) { // part of the comment comment.append(fl.line); comment.append(fl.lineSep); keyPos = in.position; } else // not a part of a comment break; fl = in.readLineExpectComment(); } // exit completely if null is returned the very first time if (firstNull) return null; String comHelp; comHelp = comment.toString(); if (comment.length() > 0) if (comment.charAt(comment.length() - 1) == '\n') comHelp = comment.substring(0, comment.length() - 1); commE = new Element.CommentElem(createBiasBounds(begPos, keyPos), comHelp); // fl now contains the line after the comment or null if none exists if (fl == null) { keyE = null; valueE = null; } else { // read the key and the value // list of ArrayList lines = new ArrayList(2); fl.startPosition = keyPos; fl.stringValue = fl.line.toString(); lines.add(fl); int nowPos; while (UtilConvert.continueLine(fl.line)) { // do something with the previous line fl.stringValue = fl.stringValue.substring(0, fl.stringValue/*fix: was: line*/.length() - 1); // now the new line nowPos = in.position; fl = in.readLineNoFrills(); if (fl == null) break; // delete the leading whitespaces int startIndex=0; for(startIndex=0; startIndex < fl.line.length(); startIndex++) if (UtilConvert.whiteSpaceChars.indexOf(fl.line.charAt(startIndex)) == -1) break; fl.stringValue = fl.line.substring(startIndex, fl.line.length()); fl.startPosition = nowPos + startIndex; lines.add(fl); } // now I have an ArrayList with strings representing lines and positions of the first non-whitespace character PositionMap pm = new PositionMap(lines); String line = pm.getString(); // Find start of key int len = line.length(); int keyStart; for(keyStart=0; keyStart<len; keyStart++) { if(UtilConvert.whiteSpaceChars.indexOf(line.charAt(keyStart)) == -1) break; } // Find separation between key and value int separatorIndex; for(separatorIndex=keyStart; separatorIndex<len; separatorIndex++) { char currentChar = line.charAt(separatorIndex); if (currentChar == '\\') separatorIndex++; else if(UtilConvert.keyValueSeparators.indexOf(currentChar) != -1) break; } // Skip over whitespace after key if any int valueIndex; for (valueIndex=separatorIndex; valueIndex<len; valueIndex++) if (UtilConvert.whiteSpaceChars.indexOf(line.charAt(valueIndex)) == -1) break; // Skip over one non whitespace key value separators if any if (valueIndex < len) if (UtilConvert.strictKeyValueSeparators.indexOf(line.charAt(valueIndex)) != -1) valueIndex++; // Skip over white space after other separators if any while (valueIndex < len) { if (UtilConvert.whiteSpaceChars.indexOf(line.charAt(valueIndex)) == -1) break; valueIndex++; } String key = line.substring(keyStart, separatorIndex); String value = (separatorIndex < len) ? line.substring(valueIndex, len) : ""; if (key == null) // PENDING - should join with the next comment ; // Convert then store key and value key = UtilConvert.loadConvert(key); value = UtilConvert.loadConvert(value); int currentPos = in.position; int valuePosFile = 0; try { valuePosFile = pm.getFilePosition(valueIndex); } catch (ArrayIndexOutOfBoundsException e) { valuePosFile = currentPos; } keyE = new Element.KeyElem (createBiasBounds(keyPos, valuePosFile), key); valueE = new Element.ValueElem(createBiasBounds(valuePosFile, currentPos), value); } return new Element.ItemElem(createBiasBounds(begPos, in.position), keyE, valueE, commE); } // ======================== PARSER METHODS =============================== // ----------------------- utilities ----------------------------------- /** Computes the real offset from the long value representing position * in the parser. * @return the offset */ static int position(long p) { return (int)(p & 0xFFFFFFFFL); } /** Creates position bounds. For obtaining the real offsets is used * previous method position() * @param begin The begin in the internal position form. * @param end The end in the internal position form. * @return the bounds */ PositionBounds createBiasBounds(long begin, long end) { PositionRef posBegin = editor.createPositionRef(position(begin), Position.Bias.Forward); PositionRef posEnd = editor.createPositionRef(position(end), Position.Bias.Backward); return new PositionBounds(posBegin, posEnd); } // ==================== Position Map ========================== /** Class which maps positions in a string to positions in the underlying file */ private static class PositionMap { private ArrayList list; /** constructor - expects a list of FlaggedLine */ PositionMap(ArrayList lines) { list = lines; } /** Returns the string represented by the object */ public String getString() { String allLines = ((FlaggedLine)list.get(0)).stringValue; for (int part=1; part<list.size(); part++) allLines += ((FlaggedLine)list.get(part)).stringValue; return allLines; } /** Returns position in the file for a position in a string * @param posString position in the string to find file position for * @return position in the file * @exception ArrayIndexOutOfBoundsException if the requested position is outside * the area represented by this object */ public int getFilePosition(int posString) throws ArrayIndexOutOfBoundsException { // get the part int part; int lengthSoFar = 0; int lastLengthSoFar = 0; for (part=0; part < list.size(); part++) { lastLengthSoFar = lengthSoFar; lengthSoFar += ((FlaggedLine)list.get(part)).stringValue.length(); // brute patch - last (cr)lf should not be the part of the thing, other should if (part == list.size() - 1) if (lengthSoFar >= posString) break; else; else if (lengthSoFar > posString) break; } if (posString > lengthSoFar) throw new ArrayIndexOutOfBoundsException("not in scope"); return ((FlaggedLine)list.get(part)).startPosition + posString - lastLengthSoFar; } } // ==================== The properties reader ========================== /** A reader which allows reading from an input stream or from a string and remembers * its position in the document. */ private static class PropertiesRead { /** The underlaying reader. */ private Reader reader; /** Position after the last character read */ public int position; /** The character that someone peeked */ private int peekChar; /** Does the initialization */ private PropertiesRead() { peekChar = -1; position = 0; } /** Creates the reader from the text. */ PropertiesRead(String text) { this(); reader = new StringReader(text); } /** Creates the reader from the another stream. */ PropertiesRead(InputStream stream) { this(); try { reader = new BufferedReader(new InputStreamReader(stream, "8859_1")); } catch (UnsupportedEncodingException e) { // impossible - this encoding is always supported } } /** Read one character from the stream and increases the position. * @return the character or -1 if the end of the stream has been reached */ public int read() throws IOException { int character = peek(); peekChar = -1; if (character != -1) position++; return character; } /** Returns the next character without increasing the position. Subsequent calls * to peek() and read() will return the same character. * @return the character or -1 if the end of the stream has been reached */ private int peek() throws IOException { if (peekChar == -1) peekChar = reader.read(); return peekChar; } /** Reads the next line and returns the flag as true if the line is a comment line. * If the input is empty returns null * Flag in the result is true if the line is a comment line */ public FlaggedLine readLineExpectComment() throws IOException { int charRead = read(); if (charRead == -1) // end of the reader reached return null; boolean decided = false; FlaggedLine fl = new FlaggedLine(); while (charRead != -1 && charRead != (int)'\n' && charRead != (int)'\r') { if (!decided) if(UtilConvert.whiteSpaceChars.indexOf((char)charRead) == -1) { // not a whitespace - decide now fl.flag = (((char)charRead == '!') || ((char)charRead == '#')); decided = true; } fl.line.append((char)charRead); charRead = read(); } if (!decided) // all were whitespaces fl.flag = true; // set the line separator if (charRead == (int)'\r') if (peek() == (int)'\n') { charRead = read(); fl.lineSep = "\r\n"; } else fl.lineSep = "\r"; else if (charRead == (int)'\n') fl.lineSep = "\n"; else fl.lineSep = System.getProperty("line.separator"); return fl; } /** Reads the next line . * If the input is empty returns null */ public FlaggedLine readLineNoFrills() throws IOException { int charRead = read(); if (charRead == -1) // end of the reader reached return null; FlaggedLine fl = new FlaggedLine(); while (charRead != -1 && charRead != (int)'\n' && charRead != (int)'\r') { fl.line.append((char)charRead); charRead = read(); } // set the line separator if (charRead == (int)'\r') if (peek() == (int)'\n') { charRead = read(); fl.lineSep = "\r\n"; } else fl.lineSep = "\r"; else if (charRead == (int)'\n') fl.lineSep = "\n"; else fl.lineSep = System.getProperty("line.separator"); return fl; } /** Closes the stream */ public void close() throws IOException { reader.close(); } } /** Helper class */ private static class FlaggedLine { FlaggedLine() { line = new StringBuffer(); flag = false; lineSep = "\n"; startPosition = 0; } StringBuffer line; boolean flag; String lineSep; int startPosition; String stringValue; } } /* * <<Log>> * 12 Gandalf 1.11 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 11 Gandalf 1.10 10/4/99 Petr Jiricka Fixed bug #4178 * 10 Gandalf 1.9 8/18/99 Petr Jiricka Fixed bug in parsing * multiline values * 9 Gandalf 1.8 8/9/99 Petr Jiricka Removed debug prints * 8 Gandalf 1.7 7/27/99 Petr Jiricka * 7 Gandalf 1.6 6/10/99 Petr Jiricka * 6 Gandalf 1.5 6/9/99 Ian Formanek ---- Package Change To * org.openide ---- * 5 Gandalf 1.4 6/6/99 Petr Jiricka * 4 Gandalf 1.3 5/16/99 Petr Jiricka * 3 Gandalf 1.2 5/14/99 Petr Jiricka * 2 Gandalf 1.1 5/13/99 Petr Jiricka * 1 Gandalf 1.0 5/12/99 Petr Jiricka * $ */